//! Statement AST nodes for NRC language

use crate::ast::expr::Expression;
use crate::error::Location;
use serde::{Deserialize, Serialize};

/// All possible statements in NRC
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Statement {
    /// Expression statement
    Expression(Expression),
    /// Variable declaration
    VariableDecl(VariableDeclStmt),
    /// Constant declaration
    ConstantDecl(ConstantDeclStmt),
    /// Assignment statement
    Assignment(AssignmentStmt),
    /// If statement
    If(IfStmt),
    /// For statement
    For(ForStmt),
    /// While statement
    While(WhileStmt),
    /// Match statement
    Match(MatchStmt),
    /// Return statement
    Return(ReturnStmt),
    /// Break statement
    Break(BreakStmt),
    /// Continue statement
    Continue(ContinueStmt),
    /// Select statement
    Select(Box<SelectStmt>),
    /// Go statement (goroutine)
    Go(GoStmt),
    /// Defer statement
    Defer(DeferStmt),
    /// Recover statement (Go-style panic recovery with pattern matching)
    Recover(RecoverStmt),
    /// Block statement
    Block(BlockStmt),
    /// Export statement
    Export(ExportStmt),
    /// Empty statement
    Empty,
}

/// Variable declaration statement
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VariableDeclStmt {
    /// Variable name
    pub name: String,
    /// Variable type (if specified)
    pub var_type: Option<Type>,
    /// Initial value
    pub initializer: Option<Expression>,
    /// Is mutable
    pub mutable: bool,
    /// Location in source
    pub location: Location,
}

/// Constant declaration statement
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConstantDeclStmt {
    /// Constant name
    pub name: String,
    /// Constant type (if specified)
    pub const_type: Option<Type>,
    /// Constant value
    pub value: Expression,
    /// Location in source
    pub location: Location,
}

/// Assignment statement
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssignmentStmt {
    /// Left-hand side (target)
    pub target: AssignmentTarget,
    /// Assignment operator
    pub operator: AssignmentOp,
    /// Right-hand side (value)
    pub value: Expression,
    /// Location in source
    pub location: Location,
}

/// Assignment target
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AssignmentTarget {
    /// Simple variable
    Variable(String),
    /// Field access
    FieldAccess(FieldAccessTarget),
    /// Index access
    IndexAccess(IndexAccessTarget),
    /// Dereference (pointer indirection)
    Deref(Box<Expression>),
}

/// Field access target
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldAccessTarget {
    /// Object expression
    pub object: Expression,
    /// Field name
    pub field: String,
    /// Location in source
    pub location: Location,
}

/// Index access target
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexAccessTarget {
    /// Object expression
    pub object: Expression,
    /// Index expression
    pub index: Expression,
    /// Location in source
    pub location: Location,
}

/// Assignment operators
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AssignmentOp {
    /// Simple assignment (=)
    Assign,
    /// Add and assign (+=)
    AddAssign,
    /// Subtract and assign (-=)
    SubAssign,
    /// Multiply and assign (*=)
    MulAssign,
    /// Divide and assign (/=)
    DivAssign,
    /// Modulo and assign (%=)
    ModAssign,
    /// Bitwise AND and assign (&=)
    BitAndAssign,
    /// Bitwise OR and assign (|=)
    BitOrAssign,
    /// Bitwise XOR and assign (^=)
    BitXorAssign,
    /// Left shift and assign (<<=)
    LeftShiftAssign,
    /// Right shift and assign (>>=)
    RightShiftAssign,
}

/// If statement
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IfStmt {
    /// Condition
    pub condition: Expression,
    /// Then branch
    pub then_branch: Box<Statement>,
    /// Else branch (if any)
    pub else_branch: Option<Box<Statement>>,
    /// Location in source
    pub location: Location,
}

/// For statement - supports Go-style for loops
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ForStmt {
    /// For loop variant
    pub variant: ForVariant,
    /// Loop body
    pub body: Box<Statement>,
    /// Location in source
    pub location: Location,
}

/// For loop variants
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ForVariant {
    /// Traditional C-style: for var i = 0; i < 10; i++ {}
    Traditional {
        /// Initialization statement (optional)
        initializer: Option<Box<Statement>>,
        /// Condition expression (optional)
        condition: Option<Expression>,
        /// Increment statement (optional)
        increment: Option<Box<Statement>>,
    },
    /// Condition-only: for boolValue {}
    Condition {
        /// Condition expression
        condition: Expression,
    },
    /// Range-based: for var v T in vec {}
    Range {
        /// Loop variable name
        variable: String,
        /// Variable type (required for now)
        variable_type: Type,
        /// Iterable expression
        iterable: Expression,
    },
}

/// While statement
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WhileStmt {
    /// Condition
    pub condition: Expression,
    /// Loop body
    pub body: Box<Statement>,
    /// Location in source
    pub location: Location,
}

/// Match statement
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MatchStmt {
    /// Expression to match
    pub expr: Expression,
    /// Match arms
    pub arms: Vec<MatchArmStmt>,
    /// Location in source
    pub location: Location,
}

/// Match arm for statements
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MatchArmStmt {
    /// Pattern
    pub pattern: Pattern,
    /// Guard condition (if any)
    pub guard: Option<Expression>,
    /// Arm body
    pub body: Statement,
    /// Location in source
    pub location: Location,
}

/// Return statement
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReturnStmt {
    /// Return value (if any)
    pub value: Option<Expression>,
    /// Location in source
    pub location: Location,
}

/// Break statement
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BreakStmt {
    /// Break label (if any)
    pub label: Option<String>,
    /// Location in source
    pub location: Location,
}

/// Continue statement
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContinueStmt {
    /// Continue label (if any)
    pub label: Option<String>,
    /// Location in source
    pub location: Location,
}

/// Select statement
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SelectStmt {
    /// Select cases
    pub cases: Vec<SelectCaseStmt>,
    /// Default case (if any)
    pub default_case: Option<Box<Statement>>,
    /// Location in source
    pub location: Location,
}

/// Select case for statements
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SelectCaseStmt {
    /// Channel expression
    pub channel: Expression,
    /// Case body
    pub body: Statement,
    /// Location in source
    pub location: Location,
}

/// Go statement (goroutine)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GoStmt {
    /// Function call to execute in goroutine
    pub call: CallExpr,
    /// Location in source
    pub location: Location,
}

/// Block statement
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockStmt {
    /// Statements in the block
    pub statements: Vec<Statement>,
    /// Location in source
    pub location: Location,
}

// Re-export types from other modules
use crate::ast::expr::{CallExpr, Pattern};
use crate::ast::types::Type;

impl Statement {
    /// Get the location of this statement
    pub fn location(&self) -> Location {
        match self {
            Statement::Expression(expr) => expr.location(),
            Statement::VariableDecl(stmt) => stmt.location,
            Statement::ConstantDecl(stmt) => stmt.location,
            Statement::Assignment(stmt) => stmt.location,
            Statement::If(stmt) => stmt.location,
            Statement::For(stmt) => stmt.location,
            Statement::While(stmt) => stmt.location,
            Statement::Match(stmt) => stmt.location,
            Statement::Return(stmt) => stmt.location,
            Statement::Break(stmt) => stmt.location,
            Statement::Continue(stmt) => stmt.location,
            Statement::Recover(stmt) => stmt.location,
            Statement::Select(stmt) => stmt.location,
            Statement::Go(stmt) => stmt.location,
            Statement::Defer(stmt) => stmt.location,
            Statement::Block(stmt) => stmt.location,
            Statement::Export(stmt) => stmt.location,
            Statement::Empty => Location::new(0, 0, 0),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_expression_statement() {
        let expr = Expression::Literal(crate::ast::expr::Literal::Integer(42));
        let stmt = Statement::Expression(expr);

        assert!(matches!(stmt, Statement::Expression(_)));
    }

    #[test]
    fn test_variable_declaration() {
        let location = Location::new(1, 1, 0);
        let stmt = Statement::VariableDecl(VariableDeclStmt {
            name: "x".to_string(),
            var_type: None,
            initializer: Some(Expression::Literal(crate::ast::expr::Literal::Integer(42))),
            mutable: true,
            location,
        });

        assert!(matches!(stmt, Statement::VariableDecl(_)));
    }

    #[test]
    fn test_assignment_statement() {
        let location = Location::new(1, 1, 0);
        let stmt = Statement::Assignment(AssignmentStmt {
            target: AssignmentTarget::Variable("x".to_string()),
            operator: AssignmentOp::Assign,
            value: Expression::Literal(crate::ast::expr::Literal::Integer(42)),
            location,
        });

        assert!(matches!(stmt, Statement::Assignment(_)));
    }

    #[test]
    fn test_return_statement() {
        let location = Location::new(1, 1, 0);
        let stmt = Statement::Return(ReturnStmt {
            value: Some(Expression::Literal(crate::ast::expr::Literal::Integer(42))),
            location,
        });

        assert!(matches!(stmt, Statement::Return(_)));
    }

    #[test]
    fn test_return_statement_no_value() {
        let location = Location::new(2, 5, 20);
        let stmt = Statement::Return(ReturnStmt {
            value: None,
            location,
        });

        assert!(matches!(stmt, Statement::Return(_)));
        assert_eq!(stmt.location().line, 2);
        assert_eq!(stmt.location().column, 5);
    }

    #[test]
    fn test_break_statement() {
        let location = Location::new(3, 10, 30);
        let stmt = Statement::Break(BreakStmt {
            label: None,
            location,
        });

        assert!(matches!(stmt, Statement::Break(_)));
        assert_eq!(stmt.location().line, 3);
        assert_eq!(stmt.location().column, 10);
    }

    #[test]
    fn test_continue_statement() {
        let location = Location::new(4, 15, 40);
        let stmt = Statement::Continue(ContinueStmt {
            label: None,
            location,
        });

        assert!(matches!(stmt, Statement::Continue(_)));
        assert_eq!(stmt.location().line, 4);
        assert_eq!(stmt.location().column, 15);
    }

    #[test]
    fn test_empty_statement() {
        let stmt = Statement::Empty;
        assert!(matches!(stmt, Statement::Empty));
        let loc = stmt.location();
        assert_eq!(loc.line, 0);
        assert_eq!(loc.column, 0);
    }

    #[test]
    fn test_if_statement() {
        let location = Location::new(5, 1, 50);
        let stmt = Statement::If(IfStmt {
            condition: Expression::Literal(crate::ast::expr::Literal::Boolean(true)),
            then_branch: Box::new(Statement::Empty),
            else_branch: None,
            location,
        });

        assert!(matches!(stmt, Statement::If(_)));
        assert_eq!(stmt.location().line, 5);
    }

    #[test]
    fn test_while_statement() {
        let location = Location::new(6, 2, 60);
        let stmt = Statement::While(WhileStmt {
            condition: Expression::Literal(crate::ast::expr::Literal::Boolean(true)),
            body: Box::new(Statement::Empty),
            location,
        });

        assert!(matches!(stmt, Statement::While(_)));
        assert_eq!(stmt.location().line, 6);
    }

    #[test]
    fn test_defer_statement() {
        let location = Location::new(7, 3, 70);
        let stmt = Statement::Defer(DeferStmt {
            expr: Expression::Literal(crate::ast::expr::Literal::Integer(42)),
            location,
        });

        assert!(matches!(stmt, Statement::Defer(_)));
        assert_eq!(stmt.location().line, 7);
    }

    #[test]
    fn test_statement_location_variable_decl() {
        let location = Location::new(10, 5, 100);
        let stmt = Statement::VariableDecl(VariableDeclStmt {
            name: "x".to_string(),
            var_type: None,
            initializer: Some(Expression::Literal(crate::ast::expr::Literal::Integer(42))),
            mutable: true,
            location,
        });

        let loc = stmt.location();
        assert_eq!(loc.line, 10);
        assert_eq!(loc.column, 5);
    }

    #[test]
    fn test_statement_location_assignment() {
        let location = Location::new(11, 6, 110);
        let stmt = Statement::Assignment(AssignmentStmt {
            target: AssignmentTarget::Variable("x".to_string()),
            operator: AssignmentOp::Assign,
            value: Expression::Literal(crate::ast::expr::Literal::Integer(42)),
            location,
        });

        let loc = stmt.location();
        assert_eq!(loc.line, 11);
        assert_eq!(loc.column, 6);
    }

    #[test]
    fn test_constant_declaration() {
        let location = Location::new(12, 7, 120);
        let stmt = Statement::ConstantDecl(ConstantDeclStmt {
            name: "PI".to_string(),
            const_type: None,
            value: Expression::Literal(crate::ast::expr::Literal::Float(3.14)),
            location,
        });

        assert!(matches!(stmt, Statement::ConstantDecl(_)));
        assert_eq!(stmt.location().line, 12);
    }
}

/// Export statement
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportStmt {
    /// Export items (what to export)
    pub items: Vec<ExportItem>,
    /// Location in source
    pub location: Location,
}

/// Defer statement
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeferStmt {
    /// Expression to be deferred
    pub expr: Expression,
    /// Location in source code
    pub location: Location,
}

/// Recover statement (Go-style panic recovery with pattern matching)
/// Syntax: recover err { pattern1 -> stmt1, pattern2 -> stmt2, _ -> stmt3 }
///
/// Design: Recover can be placed anywhere in the function, and only protects
/// code that comes AFTER it. If code BEFORE recover panics, it won't be caught.
///
/// Default behavior:
/// - If user defines `_ -> ...`, execute user-defined handler
/// - If user doesn't define `_` and no pattern matches, panic propagates upward
///
/// Example:
/// ```nr
/// func example() {
///     // This panic won't be caught by recover below
///     panic("too early")
///     
///     recover err {
///         "error1" -> println("caught:", err)
///         _ -> panic(err)  // User chooses to re-panic (or other handling)
///     }
///     
///     // This panic will be caught by recover above
///     panic("error1")
/// }
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecoverStmt {
    /// Error variable name (e.g., "err" in "recover err { ... }")
    pub error_var: String,
    /// Pattern match arms for different error types/values
    pub arms: Vec<RecoverArm>,
    /// Location in source
    pub location: Location,
}

/// Recover arm (pattern -> statement)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecoverArm {
    /// Pattern to match (can be type, value, or wildcard)
    pub pattern: Pattern,
    /// Guard condition (optional)
    pub guard: Option<Expression>,
    /// Handler body
    pub body: Statement,
    /// Location in source
    pub location: Location,
}

/// Export item
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ExportItem {
    /// Export function
    Function(String),
    /// Export variable
    Variable(String),
    /// Export type
    Type(String),
    /// Export with alias
    Alias(String, String), // original_name, alias_name
}
